package com.cmc6.common.exception;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import com.cmc6.common.constant.BaseConstant;
import com.cmc6.common.mapper.BaseNotifyMapper;
import com.cmc6.common.mapper.DictMapper;
import com.cmc6.common.model.entity.*;
import com.cmc6.common.repository.EsErrorRepository;
import com.cmc6.common.service.NotifyRefUserService;
import com.cmc6.common.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

@RestControllerAdvice
@Slf4j
public class ExceptionHandlerAdvice {

    @Value("${spring.profiles.active:prod}")
    private String profile;
    @Resource
    EsErrorRepository esErrorRepository;
    @Resource
    BaseNotifyMapper baseNotifyMapper;
    @Resource
    NotifyRefUserService notifyRefUserService;
    @Resource
    DictMapper dictMapper;

    /**
     * 参数校验异常
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ApiResult handleValidException(MethodArgumentNotValidException e) {

        e.printStackTrace();

        if (!"dev".equals(profile)) {
            try {
                ApiResult.error(BaseBizCodeEnum.PARAMETER_CHECK_ERROR);  // 这里肯定会抛出 BaseException异常
            } catch (BaseException baseException) {
                return getBaseExceptionApiResult(baseException);
            }
        }

        // 返回详细的参数校验错误信息
        Map<String, String> map = new HashMap<>(16);
        BindingResult bindingResult = e.getBindingResult();
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            String message = fieldError.getDefaultMessage();
            String field = fieldError.getField();
            map.put(field, message);
        }

        try {
            ApiResult.error(BaseBizCodeEnum.PARAMETER_CHECK_ERROR, map); // 这里肯定会抛出 BaseException异常
        } catch (BaseException baseException) {
            return getBaseExceptionApiResult(baseException);
        }

        return null; // 这里不会执行，只是为了通过语法检查
    }

    /**
     * BaseException：ApiResult.error()
     * 会输出 code和msg，给调用者，请谨慎一点使用！
     * 但是建议所有的错误都用 ApiResult.error()来进行处理，因为可以触发回滚机制
     */
    @ExceptionHandler(value = BaseException.class)
    public ApiResult<String> handleBaseException(BaseException e) {

        e.printStackTrace();

        return getBaseExceptionApiResult(e);
    }

    private ApiResult getBaseExceptionApiResult(BaseException e) {

        return JSONUtil.toBean(e.getMessage(), ApiResult.class);
    }

    /**
     * 权限不够时的异常处理
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    public ApiResult<String> handleAccessDeniedException(AccessDeniedException e) {

        e.printStackTrace();

        try {
            ApiResult.error(BaseBizCodeEnum.INSUFFICIENT_PERMISSIONS); // 这里肯定会抛出 BaseException异常
        } catch (BaseException baseException) {
            return getBaseExceptionApiResult(baseException);
        }

        return null; // 这里不会执行，只是为了通过语法检查
    }

    /**
     * 缺省异常处理，直接提示系统异常
     */
    @ExceptionHandler(value = Throwable.class)
    public ApiResult<String> handleThrowable(Throwable e) {

        e.printStackTrace();

        if ("prod".equals(profile)) {
            generateErrorLog(e); // 生成错误日志
        }

        try {
            ApiResult.sysError(); // 这里肯定会抛出 BaseException异常
        } catch (BaseException baseException) {
            return getBaseExceptionApiResult(baseException);
        }

        return null; // 这里不会执行，只是为了通过语法检查
    }

    /**
     * 生成错误日志
     */
    private void generateErrorLog(Throwable e) {

        HttpServletRequest request = RequestUtil.getRequest();
        Long currentUserId = UserUtil.getCurrentUserIdSafe();

        EsErrorDO esErrorDO = new EsErrorDO();
        if (request == null) {
            esErrorDO.setIp("");
            esErrorDO.setUri("");
        } else {
            esErrorDO.setIp(ServletUtil.getClientIP(request));
            esErrorDO.setUri(request.getRequestURI());
        }
        esErrorDO.setErrorId(IdUtil.simpleUUID());
        esErrorDO.setCategory(RequestUtil.getRequestCategoryEnum(request).getCode());
        esErrorDO.setRegion(IpUtil.getRegion(esErrorDO.getIp()));

        esErrorDO.setCreateId(currentUserId);
        esErrorDO.setErrorMsg(ExceptionUtil.stacktraceToOneLineString(e, 6000));

        ThreadUtil.execute(() -> {
            try {
                esErrorRepository.save(esErrorDO);
            } catch (DataAccessResourceFailureException ignored) {
                // 忽略这个异常，因为这里会报出 type ！= null 异常，但是新版本的 es，没有返回 type，所以忽略这个异常
            }
        });

        byte type = 1; // 1 系统错误

        DictDO dictDO = ChainWrappers.lambdaQueryChain(dictMapper).eq(DictDO::getDictKey, BaseConstant.NOTIFY_TYPE)
            .eq(DictDO::getType, 2).eq(DictDO::getValue, type).select(BaseEntityThree::getRemark).one();

        Set<Long> userIdSet = null;
        if (dictDO != null) {
            String remark = dictDO.getRemark(); // 获取配置的：菜单 id，多个用逗号拼接起来的
            if (StrUtil.isNotBlank(remark)) {
                List<String> menuIdList = StrUtil.splitTrim(remark, ",");
                // 过滤掉：长度不为 32的元素
                Set<Long> menuIdSet =
                    menuIdList.stream().map(Convert::toLong).filter(Objects::nonNull).collect(Collectors.toSet());
                userIdSet = UserUtil.getUserIdSetByMenuIdSet(menuIdSet);
            }
        }

        if (CollUtil.isNotEmpty(userIdSet)) {
            NotifyDO notifyDO = new NotifyDO();
            notifyDO.setType(type);
            notifyDO.setContent("您有新的系统错误产生，请及时处理");
            notifyDO.setTitle("【通知】系统错误");
            notifyDO.setExtraJson(JSONUtil.createObj().set(BaseConstant.MAIN_ID, esErrorDO.getErrorId()).toString());
            notifyDO.setEnableFlag(true);
            notifyDO.setCreateId(currentUserId);
            notifyDO.setUpdateId(currentUserId);

            Set<Long> finalUserIdSet = userIdSet;
            ThreadUtil.execute(() -> {

                baseNotifyMapper.insert(notifyDO); // 保存：通知主体信息

                List<NotifyRefUserDO> notifyRefUserInsertList = new ArrayList<>();
                for (Long item : finalUserIdSet) {
                    NotifyRefUserDO notifyRefUserDO = new NotifyRefUserDO();
                    notifyRefUserDO.setNotifyId(notifyDO.getId());
                    notifyRefUserDO.setUserId(item);
                    notifyRefUserDO.setReadFlag(false);

                    notifyRefUserInsertList.add(notifyRefUserDO);
                }

                // 批量保存：需要通知的用户信息
                notifyRefUserService.saveBatch(notifyRefUserInsertList);
                // socket，有新的通知
                KafkaUtil.refreshNoReadNotifyCount(finalUserIdSet);
            });
        }
    }

}
